<!DOCTYPE html>
<html lang="en-US">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <title>第4章 Vue 组件的使用 | 深入理解Vue.js实战</title>
    <meta name="description" content="作者：被删">
    
    
    <link rel="preload" href="/vue-ebook/assets/css/0.styles.b08616b1.css" as="style"><link rel="preload" href="/vue-ebook/assets/js/app.0a4e61a5.js" as="script"><link rel="preload" href="/vue-ebook/assets/js/3.846561a8.js" as="script"><link rel="preload" href="/vue-ebook/assets/js/2.eed49fdd.js" as="script"><link rel="preload" href="/vue-ebook/assets/js/18.bba4adaf.js" as="script">
    <link rel="stylesheet" href="/vue-ebook/assets/css/0.styles.b08616b1.css">
  </head>
  <body>
    <div id="app" data-server-rendered="true"><div class="theme-container"><header class="navbar"><div class="sidebar-button"><svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" role="img" viewBox="0 0 448 512" class="icon"><path fill="currentColor" d="M436 124H12c-6.627 0-12-5.373-12-12V80c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12zm0 160H12c-6.627 0-12-5.373-12-12v-32c0-6.627 5.373-12 12-12h424c6.627 0 12 5.373 12 12v32c0 6.627-5.373 12-12 12z"></path></svg></div> <a href="/vue-ebook/" class="home-link router-link-active"><!----> <span class="site-name">深入理解Vue.js实战</span></a> <div class="links"><div class="search-box"><input aria-label="Search" autocomplete="off" spellcheck="false" value=""> <!----></div> <nav class="nav-links can-hide"><div class="nav-item"><a href="/vue-ebook/" class="nav-link">概述</a></div><div class="nav-item"><a href="/vue-ebook/vue-ebook/" class="nav-link router-link-active">内容</a></div> <a href="https://github.com/godbasin/vue-ebook" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></nav></div></header> <div class="sidebar-mask"></div> <aside class="sidebar"><nav class="nav-links"><div class="nav-item"><a href="/vue-ebook/" class="nav-link">概述</a></div><div class="nav-item"><a href="/vue-ebook/vue-ebook/" class="nav-link router-link-active">内容</a></div> <a href="https://github.com/godbasin/vue-ebook" target="_blank" rel="noopener noreferrer" class="repo-link">
    Github
    <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></a></nav>  <ul class="sidebar-links"><li><section class="sidebar-group depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>前言</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-ebook/vue-ebook/0.html" class="sidebar-link">前端框架的出现</a></li></ul></section></li><li><section class="sidebar-group depth-0" style="padding-top:10px;"><div class="kitty-main" data-v-0cd75e06><span class="stand" data-v-0cd75e06></span> <div class="cat" data-v-0cd75e06><div class="body" data-v-0cd75e06></div> <div class="head" data-v-0cd75e06><div class="ear" data-v-0cd75e06></div> <div class="ear" data-v-0cd75e06></div></div> <div class="face" data-v-0cd75e06><div class="nose" data-v-0cd75e06></div> <div class="whisker-container" data-v-0cd75e06><div class="whisker" data-v-0cd75e06></div> <div class="whisker" data-v-0cd75e06></div></div> <div class="whisker-container" data-v-0cd75e06><div class="whisker" data-v-0cd75e06></div> <div class="whisker" data-v-0cd75e06></div></div></div> <div class="tail-container" data-v-0cd75e06><div class="tail" data-v-0cd75e06><div class="tail" data-v-0cd75e06><div class="tail" data-v-0cd75e06><div class="tail" data-v-0cd75e06><div class="tail" data-v-0cd75e06><div class="tail" data-v-0cd75e06><div class="tail" data-v-0cd75e06></div></div></div></div></div></div></div></div></div></div> <p class="sidebar-heading open"><span>第一部分 Vue快速入门</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-ebook/vue-ebook/1.html" class="sidebar-link">第1章 Vue 框架介绍</a></li><li><a href="/vue-ebook/vue-ebook/2.html" class="sidebar-link">第2章 Vue 环境快速搭建</a></li><li><a href="/vue-ebook/vue-ebook/3.html" class="sidebar-link">第3章 Vue 基础介绍</a></li><li><a href="/vue-ebook/vue-ebook/4.html" class="active sidebar-link">第4章 Vue 组件的使用</a><ul class="sidebar-sub-headers"><li class="sidebar-sub-header"><a href="/vue-ebook/vue-ebook/4.html#_4-1-组件系统" class="sidebar-link">4.1 组件系统</a></li><li class="sidebar-sub-header"><a href="/vue-ebook/vue-ebook/4.html#_4-2-组件使用" class="sidebar-link">4.2 组件使用</a></li><li class="sidebar-sub-header"><a href="/vue-ebook/vue-ebook/4.html#_4-3-组件间通信" class="sidebar-link">4.3 组件间通信</a></li><li class="sidebar-sub-header"><a href="/vue-ebook/vue-ebook/4.html#_4-4-组件的自我修养" class="sidebar-link">4.4 组件的自我修养</a></li></ul></li><li><a href="/vue-ebook/vue-ebook/5.html" class="sidebar-link">第5章 常用指令和自定义指令</a></li><li><a href="/vue-ebook/vue-ebook/6.html" class="sidebar-link">第6章 Vue 动画</a></li><li><a href="/vue-ebook/vue-ebook/7.html" class="sidebar-link">第7章 Vue Router 路由搭建应用</a></li><li><a href="/vue-ebook/vue-ebook/8.html" class="sidebar-link">第8章 实战：Todo List 从组件到应用</a></li></ul></section></li><li><section class="sidebar-group depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>第二部分 Vue的正确使用方式</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-ebook/vue-ebook/9.html" class="sidebar-link">第9章 思维转变与大型项目管理</a></li><li><a href="/vue-ebook/vue-ebook/10.html" class="sidebar-link">第10章 如何正确地进行抽象</a></li><li><a href="/vue-ebook/vue-ebook/11.html" class="sidebar-link">第11章 全局数据管理与 Vuex</a></li><li><a href="/vue-ebook/vue-ebook/12.html" class="sidebar-link">第12章 实战：三天开发一个管理端</a></li><li><a href="/vue-ebook/vue-ebook/13.html" class="sidebar-link">第13章 实战：表单配置化实现</a></li><li><a href="/vue-ebook/vue-ebook/14.html" class="sidebar-link">第14章 实战：使用 Webpack 或 Vue CLI 搭建多页应用</a></li><li><a href="/vue-ebook/vue-ebook/15.html" class="sidebar-link">第15章 Vue 周边拓展</a></li><li><a href="/vue-ebook/vue-ebook/16.html" class="sidebar-link">第16章 关于 Vue 3.0</a></li></ul></section></li><li><section class="sidebar-group depth-0" style="padding-top:;"><!----> <p class="sidebar-heading"><span>后记</span> <!----></p> <ul class="sidebar-links sidebar-group-items"><li><a href="/vue-ebook/vue-ebook/99.html" class="sidebar-link">关于框架选型</a></li></ul></section></li></ul> </aside> <main class="page"> <div class="theme-default-content content__default"><h1 id="第4章-vue-组件的使用"><a href="#第4章-vue-组件的使用" aria-hidden="true" class="header-anchor">#</a> 第4章 Vue 组件的使用</h1> <p>上一章我们讲到，一个 Vue 应用由根实例以及组件树（可复用的 Vue 实例）组成。我们讲了单个的 Vue 实例，本章我们来介绍多个 Vue 实例（组件）的组成——组件系统。</p> <h2 id="_4-1-组件系统"><a href="#_4-1-组件系统" aria-hidden="true" class="header-anchor">#</a> 4.1 组件系统</h2> <p>关于组件的划分，不同的人有不同的方式。而组件系统是一种抽象，允许我们使用小型、独立和通常可复用的组件构建大型应用。</p> <h3 id="_4-1-1-什么是组件"><a href="#_4-1-1-什么是组件" aria-hidden="true" class="header-anchor">#</a> 4.1.1 什么是组件</h3> <p>简答来说，<strong>组件可以扩展 HTML 元素，封装可重用的代码</strong>。</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token comment">&lt;!--长这样--&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-component</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-component</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>别看它什么都没有，这只是我们最后使用的样子，逻辑都封装在组件里面了。一个组件，它的呈现可能会千奇百怪，因为不管怎么说，组件虽然可视为个体或是实例，但也是一种抽象。</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/vue-10-8.jpg" alt="image"><br>
图 4-1 一只猫也可以是一个组件</p> <p>正如图 4-1 中的这只猫，它可以拖动，也可以鼠标悬浮弹出一个提示框，还可以双击或长按让它消失，这些逻辑也都封装在组件里，最终我们使用的时候，只需要引入组件，然后在页面里插入<code>&lt;Kitty&gt;&lt;/Kitty&gt;</code>这样一个组件就可以了。</p> <h4 id="合理的抽象"><a href="#合理的抽象" aria-hidden="true" class="header-anchor">#</a> 合理的抽象</h4> <p>几乎任意类型的应用界面都可以抽象为一个组件树，例如 Github 上 Vue 主页，我们能看到页面能划分成一块块的内容块，其中有些也可以看作组件：<br> <img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/vue-4-1.png" alt="image"><br>
图 4-2 Vue Github 主页</p> <p>一般来说，这样的一个管理页面，我们可以抽象成这样的组件树：<br> <img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/vue-4-2.jpg" alt="image"><br>
图 4-3 Vue Github 主页组件树</p> <p>以代码的方式来表达这样的组件树，通常的代码呈现是：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">id</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>app<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>app-header</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header-search</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header-search</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header-nav</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header-nav</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header-aside</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header-aside</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>app-nav</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>app-view</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>group-info</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>group-info</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>app-tab</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>app-tab</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>app-tab-container</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>project-card</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>project-card</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>card-list</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>card-list</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>app-tab-container</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>app-view</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>其实这个页面抽象的方法很多，在不同的开发者眼里，可以完全抽象成不同的树状。我们在实际开发中，具体要以怎样的逻辑或者思维去进行抽象，这些内容会在后面详述。这里我们就直接讲一些和组件相关的基础内容。</p> <h4 id="树状的组件管理"><a href="#树状的组件管理" aria-hidden="true" class="header-anchor">#</a> 树状的组件管理</h4> <p>上面我们看到，几乎所有的页面都可以抽象成组件树，但为什么是树状呢？因为我们的页面组成、DOM 节点、HTML 元素也都是树状结构的，组件说白了也就是将一部分的 HTML+CSS+Javascript 代码组合成一个可复用、更简洁表达的代码片段，因此最终呈现也是树状结构的。</p> <p>树状结构是组织和管理中十分快捷、方便又清晰的结构，我们的文件管理、思维导图等也都是基于树状结构来进行的。<a href="/vue-ebook/vue-ebook/1.html">第1章</a>中有讲，AngularJS 最初的 watch 机制是环状的，导致了一些性能问题，在升级成 Angular 之后，也调整为树状结构，性能也上去了。组件系统使用这样的方式，可以由上到下的方式来管理，不管是事件的通知、数据的检测、父子组件的通信等，都会更高效。</p> <p>讲了这么多，我们来看一下组件的定义和使用方式。</p> <h2 id="_4-2-组件使用"><a href="#_4-2-组件使用" aria-hidden="true" class="header-anchor">#</a> 4.2 组件使用</h2> <p>组件是可复用的 Vue 实例，所以它们与<code>new Vue()</code>接收相同的选项，例如<code>data</code>、<code>computed</code>、<code>watch</code>、<code>methods</code>以及生命周期钩子等（生命周期一致，可以参考<a href="/vue-ebook/vue-ebook/3.html">第3章</a>中 Vue 实例的生命周期）。除了<code>el</code>这种是根实例特有的选项。</p> <h3 id="_4-2-1-注册方式"><a href="#_4-2-1-注册方式" aria-hidden="true" class="header-anchor">#</a> 4.2.1 注册方式</h3> <p>为了能在模板中使用，这些组件必须先注册以便 Vue 能够识别。Vue 中有两种组件的注册类型：全局注册和局部注册。</p> <h4 id="组件名"><a href="#组件名" aria-hidden="true" class="header-anchor">#</a> 组件名</h4> <p>HTML 中的特性名是大小写不敏感的，所以浏览器会把所有大写字符解释为小写字符。在 Vue 中，组件命名有两种方式：<br>
(1) 使用短横线分隔(kebab-case)命名。当使用 kebab-case 定义一个组件时，你也必须在引用这个自定义元素时使用 kebab-case，如<code>&lt;my-component&gt;&lt;/my-component&gt;</code>。<br>
(2) 使用大驼峰(PascalCase)。当使用 PascalCase 定义一个组件时，既可以使用 PascalCase 也可以使用 kebab-case 来引用，像<code>&lt;my-component&gt;&lt;/my-component&gt;</code>和<code>&lt;MyComponent&gt;&lt;/MyComponent&gt;</code>。</p> <h4 id="全局注册"><a href="#全局注册" aria-hidden="true" class="header-anchor">#</a> 全局注册</h4> <p>全局注册可以通过<code>Vue.component()</code>的方式进行，该方法第一个参数要传入组件的名称，第二个参数传入该组件的选项：</p> <div class="language-js extra-class"><pre class="language-js"><code>Vue<span class="token punctuation">.</span><span class="token function">component</span><span class="token punctuation">(</span><span class="token string">&quot;my-button&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  <span class="token comment">// 选项</span>
  <span class="token comment">// 除了 el 以外，组件的选项与 Vue 实例相同</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><h4 id="局部注册"><a href="#局部注册" aria-hidden="true" class="header-anchor">#</a> 局部注册</h4> <p>局部注册可通过在实例中的<code>components</code>选项进行配置：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// 获取组件</span>
<span class="token keyword">import</span> MyButton <span class="token keyword">from</span> <span class="token string">&quot;../components/my-button&quot;</span><span class="token punctuation">;</span>

<span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  components<span class="token punctuation">:</span> <span class="token punctuation">[</span>MyButton<span class="token punctuation">]</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>而以这种方式使用组件的时候，则需要在组件里通过<code>name</code>选项进行命名：</p> <div class="language-js extra-class"><pre class="language-js"><code><span class="token comment">// my-button.vue</span>
<span class="token keyword">new</span> <span class="token class-name">Vue</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
  name<span class="token punctuation">:</span> <span class="token string">&quot;my-button&quot;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><p>这种方式定义的组件，如果也进行了全局注册，其命名会以全局注册的名字为准，也就是全局注册的命名优先级更高。</p> <h3 id="_4-2-2-单文件组件"><a href="#_4-2-2-单文件组件" aria-hidden="true" class="header-anchor">#</a> 4.2.2 单文件组件</h3> <p>前面讲到，一个组件是一些逻辑和功能完整的代码片段组成的，同时也包括了 HTML、CSS 和 Javascript 的代码。在 Vue 里，我们常常使用单文件组件，使用.vue 后缀命名的文件，一般也包括这三部分：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token comment">&lt;!-- 组件模板 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  <span class="token comment">// 组件逻辑</span>
  <span class="token comment">// 在.vue文件中，需要默认export一个Vue实例</span>
  <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
    name<span class="token punctuation">:</span> <span class="token string">&quot;MyComponent&quot;</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">&gt;</span></span><span class="token style"><span class="token language-css">
  <span class="token comment">/* 组件样式 */</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>通过这种方式，我们可以更方便地在项目中管理组件和文件。这里的样式，我们还可以通过添加<code>scoped</code>属性的方式<code>&lt;style scoped&gt;</code>，来增加组件的样式作用域。具体的实现是，通过使用 PostCSS 来实现以下转换：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>example<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>hi<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span> <span class="token attr-name">scoped</span><span class="token punctuation">&gt;</span></span><span class="token style"><span class="token language-css">
  <span class="token selector">.example</span> <span class="token punctuation">{</span>
    <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">&gt;</span></span>

<span class="token comment">&lt;!-- 会转换成以下 --&gt;</span>
<span class="token comment">&lt;!-- 通过增加局部随机属性，来匹配到具体的组件 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>example<span class="token punctuation">&quot;</span></span> <span class="token attr-name">data-v-f3f3eg9</span><span class="token punctuation">&gt;</span></span>hi<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span><span class="token punctuation">&gt;</span></span><span class="token style"><span class="token language-css">
  <span class="token selector">.example[data-v-f3f3eg9]</span> <span class="token punctuation">{</span>
    <span class="token property">color</span><span class="token punctuation">:</span> red<span class="token punctuation">;</span>
  <span class="token punctuation">}</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>除了<code>scoped</code>之外，前面我们也讲到官方的脚手架 Vue CLI 中内置了 CSS Loader 一大堆，包括 extract-css-loader、vue-style-loader、css-loader、cssnano、postcss-loader、sass-loader、less-loader 等。所以我们在使用官方脚手架的时候，也可以尽情地使用这些 CSS 预处理器，使用 lang 属性就可以：<code>&lt;style lang=&quot;scss&quot;&gt;&lt;/style&gt;</code>。</p> <p>单文件组件是 Vue 里推荐的使用方式，如果开发者不习惯在一个页面中同时维护着 HTML、CSS 和 Javascript 的话，也可以通过 src 属性等方式来引入：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token comment">&lt;!-- my-component.vue --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span>This will be pre-compiled<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>./my-component.js<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token script"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>style</span> <span class="token attr-name">src</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>./my-component.css<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token style"></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>style</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><h2 id="_4-3-组件间通信"><a href="#_4-3-组件间通信" aria-hidden="true" class="header-anchor">#</a> 4.3 组件间通信</h2> <p><a href="/vue-ebook/vue-ebook/3.html">第3章</a>中我们介绍了 Vue 实例常用的选项和模板语法，而 Vue 组件其实也是 Vue 实例，但组件系统由于树状结构而构成的父子组件等关系，则是单个 Vue 实例中没有的。因此，这里我们重点介绍一下 Vue 组件间的数据传递和通信等。</p> <h3 id="_4-3-1-prop-数据传递"><a href="#_4-3-1-prop-数据传递" aria-hidden="true" class="header-anchor">#</a> 4.3.1 Prop 数据传递</h3> <p>大多数情况下，我们会将部分的代码抽象成组件，是因为该部分的内容在别处也有使用到，而抽象成组件，可以提供更简单的复用方式。例如常用的按钮，我们可以对它进行封装。在 Vue 实例的选项中，我们可以用一个<code>props</code>选项将其包含在该组件可接受的 prop 列表中：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span>
    <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>my-button<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>handleClick<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:disabled</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>disabled || loading<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>type<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>[
      type ? 'my-button--' + type : '',
      buttonSize ? 'my-button--' + buttonSize : '',
      {
        'is-disabled': disabled,
        'is-loading': loading
      }
    ]<span class="token punctuation">&quot;</span></span>
  <span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>i</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>el-icon-loading<span class="token punctuation">&quot;</span></span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>loading<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>i</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>i</span> <span class="token attr-name">:class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>icon<span class="token punctuation">&quot;</span></span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>icon &amp;&amp; !loading<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>i</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>$slots.default<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>slot</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
    name<span class="token punctuation">:</span> <span class="token string">&quot;MyButton&quot;</span><span class="token punctuation">,</span>
    props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token comment">// 按钮类型，如info、warn、error等</span>
      type<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        type<span class="token punctuation">:</span> String<span class="token punctuation">,</span>
        <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">&quot;default&quot;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token comment">// icon类型，匹配样式</span>
      icon<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        type<span class="token punctuation">:</span> String<span class="token punctuation">,</span>
        <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token string">&quot;&quot;</span>
      <span class="token punctuation">}</span><span class="token punctuation">,</span>
      loading<span class="token punctuation">:</span> Boolean<span class="token punctuation">,</span> <span class="token comment">// 是否在加载中</span>
      disabled<span class="token punctuation">:</span> Boolean <span class="token comment">// 是否不可用</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>

    methods<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token comment">// 点击触发click事件</span>
      <span class="token function">handleClick</span><span class="token punctuation">(</span><span class="token parameter">evt</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">$emit</span><span class="token punctuation">(</span><span class="token string">&quot;click&quot;</span><span class="token punctuation">,</span> evt<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>可以看到，该组件封装了按钮类型、图标和状态，通过 prop 提供给外部配置使用。Prop 是我们可以在组件上注册的一些自定义特性，常常用于接收来自父组件的数据/属性值，我们可以直接在需要的地方使用：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-button</span><span class="token punctuation">&gt;</span></span>原按钮<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-button</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- 可以像这样给 prop 传入一个静态的值 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>info<span class="token punctuation">&quot;</span></span> <span class="token attr-name">icon</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>config<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>提示样式按钮（带管理图标）<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-button</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- 也可以通过 v-bind 动态赋值 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>info<span class="token punctuation">&quot;</span></span> <span class="token attr-name">:loading</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>提示样式按钮（加载中）<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-button</span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>error<span class="token punctuation">&quot;</span></span> <span class="token attr-name">:disabled</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>true<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>错误样式按钮（不可用）<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-button</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>一个组件默认可以拥有任意数量的 prop，任何值都可以传递给任何 prop。</p> <h4 id="prop-命名"><a href="#prop-命名" aria-hidden="true" class="header-anchor">#</a> Prop 命名</h4> <p>前面我们讲过在 Vue 中组件的命名，包括短横线分隔(kebab-case)和大驼峰(PascalCase)。我们知道，HTML 中的特性名是大小写不敏感的，因此 PascalCase 的 prop 名需要使用其等价的 kebab-case 来使用：</p> <div class="language-js extra-class"><pre class="language-js"><code>Vue<span class="token punctuation">.</span><span class="token function">component</span><span class="token punctuation">(</span><span class="token string">&quot;my-component&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  <span class="token comment">// 在 JavaScript 中是 camelCase 的</span>
  props<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">&quot;myMessage&quot;</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  template<span class="token punctuation">:</span> <span class="token string">&quot;&lt;p&gt;{{ myMessage }}&lt;/p&gt;&quot;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><div class="language-html extra-class"><pre class="language-html"><code><span class="token comment">&lt;!-- 在 HTML 中是 kebab-case 的 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-component</span> <span class="token attr-name">my-message</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>test<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-component</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><h4 id="prop-类型"><a href="#prop-类型" aria-hidden="true" class="header-anchor">#</a> Prop 类型</h4> <p><code>props</code>可以是简单的数组，或者使用对象作为替代，对象允许配置高级选项，如类型检测、自定义验证和设置默认值等等：</p> <p>表 4-1 <code>props</code>高级选项</p> <table><thead><tr><th><code>props</code>高级选项</th> <th>作用</th> <th>说明</th></tr></thead> <tbody><tr><td><code>type</code></td> <td>会检查该 prop 是否是给定的类型，否则抛出警告</td> <td>可以是下列原生构造函数中的一种：<code>String</code>、<code>Number</code>、<code>Boolean</code>、<code>Array</code>、<code>Object</code>、<code>Date</code>、<code>Function</code>、<code>Symbol</code>、任何自定义构造函数、或上述内容组成的数组</td></tr> <tr><td><code>default</code></td> <td>为该 prop 指定一个默认值</td> <td>如果该 prop 没有被传入，则换做用这个值，对象或数组的默认值必须从一个工厂函数返回</td></tr> <tr><td><code>required</code></td> <td>定义该 prop 是否是必填项，默认为<code>false</code></td> <td>在非生产环境中，如果这个值为<code>true</code>且该<code>prop</code>没有被传入的，则一个控制台警告将会被抛出</td></tr> <tr><td><code>validator</code></td> <td>自定义验证函数会将该 prop 的值作为唯一的参数代入</td> <td>在非生产环境下，如果该函数返回一个<code>false</code>的值 (也就是验证失败)，一个控制台警告将会被抛出</td></tr></tbody></table> <p>我们来直接看看代码更直观：</p> <div class="language-js extra-class"><pre class="language-js"><code>Vue<span class="token punctuation">.</span><span class="token function">component</span><span class="token punctuation">(</span><span class="token string">&quot;my-component&quot;</span><span class="token punctuation">,</span> <span class="token punctuation">{</span>
  <span class="token comment">// 简单的数组</span>
  props<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">&quot;propA&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;propB&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;propC&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;propD&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;propE&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;propF&quot;</span><span class="token punctuation">]</span><span class="token punctuation">,</span>
  <span class="token comment">// 高级配置</span>
  props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
    <span class="token comment">// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)</span>
    propA<span class="token punctuation">:</span> Number<span class="token punctuation">,</span>
    <span class="token comment">// 多个可能的类型</span>
    propB<span class="token punctuation">:</span> <span class="token punctuation">[</span>String<span class="token punctuation">,</span> Number<span class="token punctuation">]</span><span class="token punctuation">,</span>
    <span class="token comment">// 必填的字符串</span>
    propC<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      type<span class="token punctuation">:</span> String<span class="token punctuation">,</span>
      required<span class="token punctuation">:</span> <span class="token boolean">true</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token comment">// 带有默认值的数字</span>
    propD<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      type<span class="token punctuation">:</span> Number<span class="token punctuation">,</span>
      <span class="token keyword">default</span><span class="token punctuation">:</span> <span class="token number">100</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token comment">// 带有默认值的对象</span>
    propE<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      type<span class="token punctuation">:</span> Object<span class="token punctuation">,</span>
      <span class="token comment">// 对象或数组默认值必须从一个工厂函数获取</span>
      <span class="token function-variable function">default</span><span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token punctuation">{</span> message<span class="token punctuation">:</span> <span class="token string">&quot;hello&quot;</span> <span class="token punctuation">}</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token comment">// 自定义验证函数</span>
    propF<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token function-variable function">validator</span><span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">value</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// 这个值必须匹配下列字符串中的一个</span>
        <span class="token comment">// 当 prop 验证失败的时候，(开发环境构建版本的) Vue 将会产生一个控制台的警告。</span>
        <span class="token keyword">return</span> <span class="token punctuation">[</span><span class="token string">&quot;success&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;warning&quot;</span><span class="token punctuation">,</span> <span class="token string">&quot;danger&quot;</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token function">indexOf</span><span class="token punctuation">(</span>value<span class="token punctuation">)</span> <span class="token operator">!==</span> <span class="token operator">-</span><span class="token number">1</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
</code></pre></div><h3 id="_4-3-2-父子组件通信"><a href="#_4-3-2-父子组件通信" aria-hidden="true" class="header-anchor">#</a> 4.3.2 父子组件通信</h3> <p>所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定：父级 prop 的更新会向下流动到子组件中，但是反过来则不行。除了父组件给子组件传递数据之外，有时候我们也需要在子组件中和父级组件进行沟通。在 Vue 中，父级组件可以像处理原生的 DOM 事件一样通过<code>v-on</code>监听子组件实例的任意事件:</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-button</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>handleClick<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>自定义按钮<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-button</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><h4 id="自定义事件"><a href="#自定义事件" aria-hidden="true" class="header-anchor">#</a> 自定义事件</h4> <p>上一章我们在介绍 Vue 常用的模板语法时，也讲到了事件绑定。除了原生的 DOM 事件之外，Vue 里还提供了自定义事件系统。子组件可以通过调用内建的<code>$emit</code>方法，并传入事件名称来触发一个事件：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">v-on:</span>click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>$emit('test')<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- $emit 可以通过参数传值 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name"><span class="token namespace">v-on:</span>click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>$emit('plus', 1)<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>Plus One<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>这时候，父组件可以通过<code>v-on</code>监听(参数值将会作为参数传入这个方法):</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-button</span> <span class="token attr-name">@plus</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>handlePlus<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>自定义按钮<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-button</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
    methods<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token function">handlePlus</span><span class="token punctuation">(</span><span class="token parameter">num</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span>menberNum <span class="token operator">+=</span> num<span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>通过 prop 和自定义事件，已经可以实现了父子组件间的双向通信了。而其实在 Vue 中还可以有更多的组件间、全局的通信方式，会在后面章节中阐述。</p> <h3 id="_4-3-3-slot-插槽"><a href="#_4-3-3-slot-插槽" aria-hidden="true" class="header-anchor">#</a> 4.3.3 slot 插槽</h3> <p>前面我们在自定义<code>&lt;my-button&gt;</code>组件中，有使用到 slot 这个 DOM 元素。Vue 实现了一套内容分发的 API，将<code>&lt;slot&gt;</code>元素作为承载分发内容的出口。这个自定义的<code>&lt;slot&gt;</code>元素，可以让我们向一个组件传递自定义的内容。</p> <p>还是回到<code>&lt;my-button&gt;</code>组件：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>my-button<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>i</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>el-icon-loading<span class="token punctuation">&quot;</span></span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>loading<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>i</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>i</span> <span class="token attr-name">:class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>icon<span class="token punctuation">&quot;</span></span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>icon &amp;&amp; !loading<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>i</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>slot</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>我们可以给该组件添加内容，添加的内容会替换<code>&lt;slot&gt;&lt;/slot&gt;</code>：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-button</span><span class="token punctuation">&gt;</span></span>按钮<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-button</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- 最终会变成这样 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span>按钮<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-button</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span><span class="token punctuation">&gt;</span></span>test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>按钮<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-button</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- 最终会变成这样 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span><span class="token punctuation">&gt;</span></span>test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>按钮<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-button</span> <span class="token attr-name">loading</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span><span class="token punctuation">&gt;</span></span>test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>按钮<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-button</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- 最终会变成这样 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>i</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>el-icon-loading<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>i</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>a</span><span class="token punctuation">&gt;</span></span>test<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>a</span><span class="token punctuation">&gt;</span></span>按钮<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><h4 id="默认内容"><a href="#默认内容" aria-hidden="true" class="header-anchor">#</a> 默认内容</h4> <p>我们可以在<code>&lt;slot&gt;&lt;/slot&gt;</code>元素里，为一个插槽设置具体的后备 (也就是默认的) 内容：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>my-button<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span><span class="token punctuation">&gt;</span></span>button<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>slot</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>我们在使用的时候，会这样渲染：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-button</span><span class="token punctuation">&gt;</span></span>按钮<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-button</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- 最终会变成这样 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>按钮<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-button</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-button</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- 在没有提供内容的时候被渲染 --&gt;</span>
<span class="token comment">&lt;!-- 最终会变成这样 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>button<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><h4 id="具名插槽"><a href="#具名插槽" aria-hidden="true" class="header-anchor">#</a> 具名插槽</h4> <p>有时我们需要多个插槽，<code>&lt;slot&gt;</code>元素可以通过 name 特性来定义额外的插槽。例如我们封装了一个弹窗组件：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token comment">&lt;!-- my-dialog.vue --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>dialog-container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span><span class="token punctuation">&gt;</span></span>
    <span class="token comment">&lt;!-- 弹窗头部内容 --&gt;</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>header<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>提示<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>slot</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">&gt;</span></span>
    <span class="token comment">&lt;!-- 弹窗主要内容 --&gt;</span>
    <span class="token comment">&lt;!-- 一个不带 name 的 &lt;slot&gt; 出口会带有隐含的名字“default” --&gt;</span>
    <span class="token comment">&lt;!-- 相当于 &lt;slot name=&quot;default&quot;&gt;&lt;/slot&gt; --&gt;</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>slot</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>footer</span><span class="token punctuation">&gt;</span></span>
    <span class="token comment">&lt;!-- 弹窗底部内容，通常是按钮 --&gt;</span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>slot</span> <span class="token attr-name">name</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>footer<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token comment">&lt;!-- 设置了默认内容 --&gt;</span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>$emit('confirm')<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>确定<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>slot</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>footer</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>使用的时候，我们可以在一个<code>&lt;template&gt;</code>元素上使用<code>v-slot</code>指令（注意<code>v-slot</code>只能添加在一个<code>&lt;template&gt;</code>上，只有默认插槽时，组件的标签才可以被当作插槽的模板来使用）：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-dialog</span><span class="token punctuation">&gt;</span></span>确认要关闭吗？<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-dialog</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- 任何没有被包裹在带有 v-slot 的 &lt;template&gt; 中的内容都会被视为默认插槽的内容 --&gt;</span>
<span class="token comment">&lt;!-- 最终会变成这样 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>dialog-container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span><span class="token punctuation">&gt;</span></span>提示<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">&gt;</span></span>确认要关闭吗？<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>footer</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>$emit('confirm')<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>确定<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>footer</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-dialog</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span> <span class="token attr-name"><span class="token namespace">v-slot:</span>header</span><span class="token punctuation">&gt;</span></span>请确认<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>确认要关闭吗？<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span> <span class="token attr-name"><span class="token namespace">v-slot:</span>footer</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>确定<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>取消<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-dialog</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- 最终会变成这样 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>dialog-container<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>header</span><span class="token punctuation">&gt;</span></span>请确认<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>header</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>main</span><span class="token punctuation">&gt;</span></span>确认要关闭吗？<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>main</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>footer</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>确定<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>取消<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>footer</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p><code>v-slot</code>也有缩写，即把参数之前的所有内容 (<code>v-slot:</code>) 替换为字符<code>#</code>：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-dialog</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span> <span class="token attr-name"><span class="token namespace">v-slot:</span>header</span><span class="token punctuation">&gt;</span></span>请确认<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>确认要关闭吗？<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span> <span class="token attr-name"><span class="token namespace">v-slot:</span>footer</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>确定<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>取消<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-dialog</span><span class="token punctuation">&gt;</span></span>
<span class="token comment">&lt;!-- 可以这么进行缩写 --&gt;</span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-dialog</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span> <span class="token attr-name">#header</span><span class="token punctuation">&gt;</span></span>请确认<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>确认要关闭吗？<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span> <span class="token attr-name">#footer</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>确定<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>button</span><span class="token punctuation">&gt;</span></span>取消<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>button</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-dialog</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>以上内容是 Vue 中组件的基础内容，而在真实工作中，通常烦扰我们的不是一些基本语法的使用，而是如何进行组件的划分。关于组件的划分，我们来介绍一些好用的经验吧。</p> <h2 id="_4-4-组件的自我修养"><a href="#_4-4-组件的自我修养" aria-hidden="true" class="header-anchor">#</a> 4.4 组件的自我修养</h2> <p>组件的划分通常分成两个方式：<br>
(1) 【设计上】视觉和交互上是一个完整的组件。<br>
(2) 【实现上】写代码的时候，可重复的内容即可视为一个组件。</p> <h3 id="_4-4-1-通过视觉和交互划分"><a href="#_4-4-1-通过视觉和交互划分" aria-hidden="true" class="header-anchor">#</a> 4.4.1 通过视觉和交互划分</h3> <p>第一个角度，<strong>（设计上）视觉和交互上是一个完整的组件</strong>。</p> <p>通常来说，组件的划分，与视觉、交互等密切相关，我们可通过功能、独立性来判断是否适合作为一个组件。这里挑了个视频网站的样式，如图 4-4：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/vue-10-5.jpg" alt="image"><br>
图 4-4 某视频网站首页组件划分方式一</p> <p>我们第一眼看上去可区分出独立的视图和功能的，则可以列为这种组件的划分。或许也可以成为区块，是为视觉和交互上完整的组件。其实这样的划分，也是需要不断地在实现过程中思考和总结。而通常情况下，设计、交互以及各种开发，都能将这样的一块内容划分出来，我们可以作为一张带图片、标题和描述的卡片来看待。</p> <p>如果用代码表示，这样一张卡片的使用应该是：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>card</span> <span class="token attr-name">url</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>xxx<span class="token punctuation">&quot;</span></span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>喵喵喵<span class="token punctuation">&quot;</span></span> <span class="token attr-name">text</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>喵喵喵喵喵<span class="token punctuation">&quot;</span></span> <span class="token attr-name">info</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>58:43<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>card</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><h3 id="_4-4-2-通过代码复用划分"><a href="#_4-4-2-通过代码复用划分" aria-hidden="true" class="header-anchor">#</a> 4.4.2 通过代码复用划分</h3> <p>第二个角度，<strong>（实现上）写代码的时候，可重复的内容即可视为一个组件</strong>。还是这个页面，我们看：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/vue-10-7.jpg" alt="image"><br>
图 4-5 某视频网站首页组件划分方式二</p> <p>这里，我们能看到这种卡片形式的内容，存在页面中的各个地方。而框住的这一整块内容，在代码实现上其实是一致的，所以我们也可以同样将他们划分为一个组件。这样的话，用代码表示应该是：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>card-container</span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>你喜欢的类型，这里都有<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>card</span>
    <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card in cardList1<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:url</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.url<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.title<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:text</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.text<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:info</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.info<span class="token punctuation">&quot;</span></span>
  <span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>card</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>card-container</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>card-container</span> <span class="token attr-name">title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>你不喜欢的类型，这里也有<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>card</span>
    <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card in cardList2<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:url</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.url<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.title<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:text</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.text<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:info</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.info<span class="token punctuation">&quot;</span></span>
  <span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>card</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>card-container</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>这种情况下，我们将卡片封装成一个简单的组件，包括图片+文字描述。同时，也将有重复的部分代码，我们将它抽象成一个<code>&lt;card-container&gt;</code>的组件。这种方式的组件划分，在理解上或许没有从业务上划分的直观，会有人觉得这里就一行字，为什么我还得封装成一个组件呢？我完全可用这么写：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>你喜欢的类型，这里都有<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>card</span>
  <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card in cardList1<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name">:url</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.url<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name">:title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.title<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name">:text</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.text<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name">:info</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.info<span class="token punctuation">&quot;</span></span>
<span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>card</span><span class="token punctuation">&gt;</span></span>

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>p</span><span class="token punctuation">&gt;</span></span>你不喜欢的类型，这里也有<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>p</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>card</span>
  <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card in cardList2<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name">:url</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.url<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name">:title</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.title<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name">:text</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.text<span class="token punctuation">&quot;</span></span>
  <span class="token attr-name">:info</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card.info<span class="token punctuation">&quot;</span></span>
<span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>card</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>的确这样写也是很简单的，而且还减少了一个组件的维护。至于到哪种程度才需要单独封装一个组件呢，这需要你不断地探索和实践，同时也需要和合作伙伴一起商量讨论。很多时候，我们要正确地做好抽象和组件划分，也少不了设计同学的配合。</p> <p>怎样才能算是一个合格的组件呢？我们在设计的时候，经常要考虑解耦，但很多时候，过度的解耦反而会导致项目复杂度变高，维护性降低。总的来看，使用设计角度或者实现角度来进行封装，哪种方式效率高一些也都不好说。（温馨提示：在一个团队内，最好是使用一种方式来进行划分。因为对于成员的相互配合和项目的维护来说，统一的规范是比较重要的。）</p> <h3 id="_4-4-3-组件的封装"><a href="#_4-4-3-组件的封装" aria-hidden="true" class="header-anchor">#</a> 4.4.3 组件的封装</h3> <p>个人认为，一个称职的组件，是以下形式的：</p> <p>表 4-2 怎样才算一个称职的组件</p> <table><thead><tr><th>能力描述</th> <th>Vue 中对应属性</th></tr></thead> <tbody><tr><td>组件内维护自身的数据和状态</td> <td><code>data</code></td></tr> <tr><td>组件内维护自身的事件</td> <td><code>methods</code></td></tr> <tr><td>通过提供配置的方式，来控制展示，或者控制执行逻辑</td> <td><code>props</code></td></tr> <tr><td>通过一定的方式（事件触发/监听、API 提供），提供与外界（如父组件）通信的方式</td> <td><code>$emit</code>、<code>$on</code>、<code>ref</code></td></tr></tbody></table> <p>笼统地概括下，就是我们尽量需要把组件进行隔离，拥有独立的个体空间，同时保持与外界适当的联系。</p> <h4 id="组件内维护自身的数据和状态"><a href="#组件内维护自身的数据和状态" aria-hidden="true" class="header-anchor">#</a> 组件内维护自身的数据和状态</h4> <p>这个比较好理解，以这样一个视频的小卡片为例子：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/vue-10-11.jpg" alt="image"><br>
图 4-6 视频小卡片</p> <p>这个小卡片，它维护着自己的数据：封面图、描述、头像、作者。还有一个初始的状态，就是目前我们看到的样子。这些内容保存在组件自己的 scope 里，每个卡片组件都拥有自己的数据和状态。</p> <h4 id="组件内维护自身的事件"><a href="#组件内维护自身的事件" aria-hidden="true" class="header-anchor">#</a> 组件内维护自身的事件</h4> <p>我们在把鼠标放在卡片上，随着鼠标的位置，顶部会有个小小的进度条，同时封面图会改变，如图：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/vue-10-14.jpg" alt="image"><br>
图 4-7 视频小卡片进度条改变</p> <p>每个小卡片都有自己 mousemove 事件。当然，这里面维护了一个鼠标位置的状态，同时根据鼠标位置来控制图片的展示。</p> <h4 id="通过初始化事件，来初始化组件状态，激活组件"><a href="#通过初始化事件，来初始化组件状态，激活组件" aria-hidden="true" class="header-anchor">#</a> 通过初始化事件，来初始化组件状态，激活组件</h4> <p>组件的数据，大多数需要外界提供并传入，故可通过初始化事件来激活。</p> <h4 id="对外提供配置项，来控制展示以及具体功能"><a href="#对外提供配置项，来控制展示以及具体功能" aria-hidden="true" class="header-anchor">#</a> 对外提供配置项，来控制展示以及具体功能</h4> <p>可以通过配置的方式，来控制组件的展示和功能。</p> <p>我们看这个小卡片：<br> <img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/vue-10-15.jpg" alt="image"><br>
图 4-8 另一种视频小卡片</p> <p>和上面的不一样，左下角展示的是视频时长，而不是头像和名字，这种我们可以通过配置的方式来控制。</p> <h4 id="通过对外提供查询接口，可获取组件状态"><a href="#通过对外提供查询接口，可获取组件状态" aria-hidden="true" class="header-anchor">#</a> 通过对外提供查询接口，可获取组件状态</h4> <p>很多时候，组件独立维护着自身的数据和状态，但有些场景下，父组件或者应用需要知道组件当前的状态，故我们需要对外提供接口，以供查询。像 Vue 中，便可以通过<code>vm.$refs</code>来获取子组件的实例。</p> <h3 id="_4-4-4-独立的组件"><a href="#_4-4-4-独立的组件" aria-hidden="true" class="header-anchor">#</a> 4.4.4 独立的组件</h3> <p>组件的独立性，可以包括以下几个方面：</p> <ul><li>维护自身的数据和状态、作用域</li> <li>维护自身的事件</li></ul> <p>拿一个常见的内容卡片来看：</p> <p><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/vue-10-16.jpg" alt="image"><br>
图 4-9 内容小卡片</p> <p>这是个独立的卡片，它拥有以下的数据、状态和事件：<br>
(1) 内容数据，包括标题、文字、图片、点赞数、评论数、日期等。<br>
(2) 状态数据，是否已点赞、是否已收藏、是否详细展示内容、是否展示评论等。<br>
(3) 事件，包括点击分享、收藏、点赞、回复等等。</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>{{cardInfo.question}}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">:class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>isContextShown ? 'content-detail' : 'content-brief'<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>cardInfo.withImage<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">:url</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>cardInfo.imageUrl<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span>{{cardInfo.content}}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>likeIt()<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>点赞<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>keepIt()<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>收藏<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
    name<span class="token punctuation">:</span> <span class="token string">&quot;my-card&quot;</span><span class="token punctuation">,</span>
    props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token comment">// 传入卡片信息</span>
      cardInfo<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        type<span class="token punctuation">:</span> Object<span class="token punctuation">,</span>
        <span class="token function-variable function">default</span><span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token function">data</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span>
        isContextShown<span class="token punctuation">:</span> <span class="token boolean">false</span> <span class="token comment">// 是否省略</span>
      <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    methods<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token function">likeIt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span> <span class="token comment">// 点赞</span>
      <span class="token function">keepIt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> <span class="token comment">// 收藏</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token function">mounted</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>简化后大概就是这样一个组件。我们来看看数据这部分，正如前面说过组件和数据抽离，那么我们可以获取到两部分的内容：<br>
(1) 通过<code>props</code>从外部注入的数据，例如这个卡片的问题、回答、是否带图片、是否已收藏、是否已点赞等等。<br>
(2) 组件自己维护的数据<code>data</code>，例如这个卡片是被展开还是被省略。</p> <p>两部分的数据划分，区别常常在于这个状态是否依赖外界，或是与外界有联系。如果我们一整个页面里有很多的卡片，但同时只允许展开一个，这种情况下我们可以选择把<code>isContextShown</code>也放到<code>props</code>里，通过外部控制。</p> <h3 id="_4-4-5-组件与外界"><a href="#_4-4-5-组件与外界" aria-hidden="true" class="header-anchor">#</a> 4.4.5 组件与外界</h3> <p>我们在保持组件独立性的时候，当然还需要考虑它与外界的交互，主要包括两个方面。</p> <p><strong>(1) 对外提供配置项，来控制展示以及具体功能。</strong></p> <p>这里最简单的，我们每个卡片都需要传入内容，我们一次性拉取列表数据，并传入每个卡片，在 Vue 中可以使用 props。</p> <p><strong>(2) 对外提供查询接口，可从外界获取组件事件和状态。</strong></p> <p>这个的话，更多时候我们是通过事件等方式来告诉外界一些事情。我们前面也做了个假设，一个页面只允许一个卡片内容处于详细展开状态，除了把<code>isContextShown</code>放到外部控制的方法之外，我们还需要通过事件通知外部该状态的变更：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>h2</span><span class="token punctuation">&gt;</span></span>{{cardInfo.question}}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>h2</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span>
      <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>toggleContext()<span class="token punctuation">&quot;</span></span>
      <span class="token attr-name">:class</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>cardInfo.isContextShown ? 'content-detail' : 'content-brief'<span class="token punctuation">&quot;</span></span>
    <span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span> <span class="token attr-name">v-if</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>cardInfo.withImage<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>img</span> <span class="token attr-name">:url</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>cardInfo.imageUrl<span class="token punctuation">&quot;</span></span> <span class="token punctuation">/&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span>{{cardInfo.content}}<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>div</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>likeIt()<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>点赞<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
      <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>span</span> <span class="token attr-name">@click</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>keepIt()<span class="token punctuation">&quot;</span></span><span class="token punctuation">&gt;</span></span>收藏<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>span</span><span class="token punctuation">&gt;</span></span>
    <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>div</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
    name<span class="token punctuation">:</span> <span class="token string">&quot;my-card&quot;</span><span class="token punctuation">,</span>
    props<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token comment">// (1) 对外提供配置项，来控制展示以及具体功能</span>
      cardInfo<span class="token punctuation">:</span> <span class="token punctuation">{</span>
        type<span class="token punctuation">:</span> Object<span class="token punctuation">,</span>
        <span class="token function-variable function">default</span><span class="token punctuation">:</span> <span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    methods<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token function">likeIt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token function">keepIt</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span><span class="token punctuation">,</span>
      <span class="token function">toggleContext</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token comment">// (2) 通过事件通知，通知外界状态变更，以及最新的状态</span>
        <span class="token keyword">this</span><span class="token punctuation">.</span><span class="token function">$emit</span><span class="token punctuation">(</span><span class="token string">&quot;toggle&quot;</span><span class="token punctuation">,</span> <span class="token keyword">this</span><span class="token punctuation">.</span>cardInfo<span class="token punctuation">)</span><span class="token punctuation">;</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    <span class="token function">mounted</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span><span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>简单调整之后，我们会这样使用：</p> <div class="language-html extra-class"><pre class="language-html"><code><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>template</span><span class="token punctuation">&gt;</span></span>
  <span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>my-card</span>
    <span class="token attr-name">v-for</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card in cardList<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">:cardInfo</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>card<span class="token punctuation">&quot;</span></span>
    <span class="token attr-name">@toggle</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">&quot;</span>toggleCard<span class="token punctuation">&quot;</span></span>
  <span class="token punctuation">&gt;</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>my-card</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>template</span><span class="token punctuation">&gt;</span></span>
<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>script</span><span class="token punctuation">&gt;</span></span><span class="token script"><span class="token language-javascript">
  <span class="token keyword">export</span> <span class="token keyword">default</span> <span class="token punctuation">{</span>
    <span class="token function">data</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
      <span class="token keyword">return</span> <span class="token punctuation">{</span>
        cardList<span class="token punctuation">:</span> <span class="token punctuation">[</span>
          <span class="token comment">// ...此处省略</span>
        <span class="token punctuation">]</span>
      <span class="token punctuation">}</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">,</span>
    methods<span class="token punctuation">:</span> <span class="token punctuation">{</span>
      <span class="token function">toggleCard</span><span class="token punctuation">(</span><span class="token parameter">$event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
        <span class="token keyword">const</span> cardInfo <span class="token operator">=</span> $event<span class="token punctuation">.</span>target<span class="token punctuation">.</span>value<span class="token punctuation">;</span>
        <span class="token keyword">if</span> <span class="token punctuation">(</span>cardInfo<span class="token punctuation">.</span>isContextShown<span class="token punctuation">)</span> <span class="token punctuation">{</span>
          <span class="token comment">// 如果该卡片原先未省略状态，展开后把其他卡片也省略</span>
          <span class="token keyword">this</span><span class="token punctuation">.</span>cardList <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>cardList<span class="token punctuation">.</span><span class="token function">map</span><span class="token punctuation">(</span><span class="token parameter">card</span> <span class="token operator">=&gt;</span> <span class="token punctuation">{</span>
            <span class="token keyword">return</span> <span class="token punctuation">{</span>
              <span class="token operator">...</span>card<span class="token punctuation">,</span>
              isContextShown<span class="token punctuation">:</span> cardInfo<span class="token punctuation">.</span>id <span class="token operator">===</span> card<span class="token punctuation">.</span>id
            <span class="token punctuation">}</span><span class="token punctuation">;</span>
          <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token punctuation">{</span>
          <span class="token comment">// 如果该卡片原先展开状态，则把该卡片省略就可以</span>
          <span class="token keyword">const</span> cardIndex <span class="token operator">=</span> <span class="token keyword">this</span><span class="token punctuation">.</span>cardList<span class="token punctuation">.</span><span class="token function">findIndex</span><span class="token punctuation">(</span><span class="token parameter">x</span> <span class="token operator">=&gt;</span> x<span class="token punctuation">.</span>id <span class="token operator">===</span> cardInfo<span class="token punctuation">.</span>id<span class="token punctuation">)</span><span class="token punctuation">;</span>
          <span class="token comment">// Vue不能检测以下数组的变动，故需要使用Vue.set</span>
          Vue<span class="token punctuation">.</span><span class="token function">set</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">.</span>cardList<span class="token punctuation">,</span> cardIndex<span class="token punctuation">,</span> <span class="token punctuation">{</span>
            <span class="token operator">...</span>cardInfo<span class="token punctuation">,</span>
            isContextShown<span class="token punctuation">:</span> <span class="token boolean">false</span>
          <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        <span class="token punctuation">}</span>
      <span class="token punctuation">}</span>
    <span class="token punctuation">}</span>
  <span class="token punctuation">}</span><span class="token punctuation">;</span>
</span></span><span class="token tag"><span class="token tag"><span class="token punctuation">&lt;/</span>script</span><span class="token punctuation">&gt;</span></span>
</code></pre></div><p>这是最简单的对内和对外的联系，对一个组件来说，它也有 in 和 out 两个方向的流动。在 Vue 里，如果父组件需要获取子组件的实例，也可以通过<code>vm.$refs</code>来获取。</p> <p>以上，结束了本章 Vue 组件的内容，包括基本语法、API，还有常见的组件封装原则和划分方式。当然，这些内容并不是一定的，书中的内容永远是死的，而我们面临的问题往往却是奇形怪状的，但是思考依然是一刻不能少。所以，本章内容更重要的是给大家一个参考的逻辑思维，思考和总结才是不断进步的方式。</p></div> <!----> <footer class="page-edit"><div class="edit-link"><a href="https://github.com/godbasin/vue-ebook/edit/ebook-sourcecode/docs/vue-ebook/4.md" target="_blank" rel="noopener noreferrer">帮阿猪改善此页面！</a> <svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true" x="0px" y="0px" viewBox="0 0 100 100" width="15" height="15" class="icon outbound"><path fill="currentColor" d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"></path> <polygon fill="currentColor" points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"></polygon></svg></div> <!----> <div style="margin-top:30px;"><div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-sm-0 el-col-md-2 el-col-lg-4" style="padding-left:10px;padding-right:10px;display:block;"><div style="width:1px;height:1px;"></div></div> <div class="el-col el-col-24 el-col-sm-24 el-col-md-18 el-col-lg-16" style="padding-left:10px;padding-right:10px;"><div class="el-card box-card is-always-shadow"><div class="el-card__header"><div class="clearfix"><span>温馨提示喵</span></div></div><div class="el-card__body"> <div class="el-row" style="margin-left:-10px;margin-right:-10px;"><div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="el-image"><div class="image-slot"><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/loading.gif" style="width:100%;"></div><!----></div></div> <div class="el-col el-col-24 el-col-xs-24 el-col-sm-12" style="padding-left:10px;padding-right:10px;"><div class="copyright-text"><div>本书采用“保持署名—非商用”<a href="http://creativecommons.org/licenses/by-nc/4.0/" target="_blank">创意共享4.0许可证</a>。只要保持原作者署名和非商用，您可以自由地阅读、分享、修改本书。</div> <div>作者：<a href="https://github.com/godbasin" target="_blank">被删</a></div></div></div></div></div></div></div></div></div></footer> <div class="page-nav"><p class="inner"><span class="prev">
        ←
        <a href="/vue-ebook/vue-ebook/3.html" class="prev">
          第3章 Vue 基础介绍
        </a></span> <span class="next"><a href="/vue-ebook/vue-ebook/5.html">
          第5章 常用指令和自定义指令
        </a>
        →
      </span></p></div>  <div class="gitalk-container theme-default-content"><div id="gitalk-container" class="content"></div></div></main> <div id="kitty-container"><span><div role="tooltip" id="el-popover-8758" aria-hidden="true" class="el-popover el-popper" style="width:undefinedpx;display:none;"><!----><img src="https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/2code2.jpg" class="image"> <div class="text">牡羊猪的猫粮罐</div> </div><div id="kitty" style="background:url(https://github-imglib-1255459943.cos.ap-chengdu.myqcloud.com/assets/img/kitty0.svg);"></div></span> <div class="el-dialog__wrapper" style="display:none;"><div role="dialog" aria-modal="true" aria-label="牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）" class="el-dialog" style="margin-top:15vh;"><div class="el-dialog__header"><span class="el-dialog__title">牡羊猪是这样渐渐胖成猪的喵（点击图片可以切换噢）</span><button type="button" aria-label="Close" class="el-dialog__headerbtn"><i class="el-dialog__close el-icon el-icon-close"></i></button></div><!----><!----></div></div></div></div><div class="global-ui"></div></div>
    <script src="/vue-ebook/assets/js/app.0a4e61a5.js" defer></script><script src="/vue-ebook/assets/js/3.846561a8.js" defer></script><script src="/vue-ebook/assets/js/2.eed49fdd.js" defer></script><script src="/vue-ebook/assets/js/18.bba4adaf.js" defer></script>
  </body>
</html>
